#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 2025/11/2 15:11
# @Author  : summer
# @Email   : 610658552@qq.com
# @File    : util_wx.py
# @Software: PyCharm
# @Desc    :
import requests
import time
from typing import Optional, Dict, Any


class WechatOfficialAccountMsg:
    """
    微信公众号消息发送类
    支持功能：
    1. 自动获取/刷新微信公众号 Access Token
    2. 发送模板消息（需公众号已开通模板消息功能）
    3. 发送文本消息（需公众号已认证，且用户已关注）
    """
    # 微信公众号接口基础URL
    _TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token"
    _TEMPLATE_MSG_URL = "https://api.weixin.qq.com/cgi-bin/message/template/send"
    _TEXT_MSG_URL = "https://api.weixin.qq.com/cgi-bin/message/custom/send"

    def __init__(self, app_id: str, app_secret: str):
        """
        初始化公众号配置
        :param app_id: 公众号开发者ID（从公众号后台「开发-基本配置」获取）
        :param app_secret: 公众号开发者密码（同上）
        """
        self.app_id = app_id
        self.app_secret = app_secret
        # 存储 Access Token 及过期时间（默认1小时过期，提前200秒刷新）
        self._access_token: Optional[str] = None
        self._token_expire_time: float = 0.0  # 过期时间（时间戳）

    def _get_access_token(self) -> str:
        """
        内部方法：获取/刷新 Access Token
        逻辑：Token 未获取或已过期时，重新请求接口获取
        :return: 有效的 Access Token
        """
        # 检查 Token 是否有效（当前时间 < 过期时间 - 200秒，避免临界过期）
        current_time = time.time()
        if self._access_token and current_time < (self._token_expire_time - 200):
            return self._access_token

        # 请求新的 Token
        params = {
            "grant_type": "client_credential",
            "appid": self.app_id,
            "secret": self.app_secret
        }
        try:
            response = requests.get(url=self._TOKEN_URL, params=params, timeout=10)
            response.raise_for_status()  # 触发 HTTP 错误（如404、500）
            result = response.json()

            # 处理接口错误（如 appid 错误）
            if "errcode" in result and result["errcode"] != 0:
                raise ValueError(f"获取 Token 失败：{result['errmsg']}（错误码：{result['errcode']}）")

            # 存储 Token 及过期时间（expires_in 单位：秒，默认7200）
            self._access_token = result["access_token"]
            self._token_expire_time = current_time + result["expires_in"]
            return self._access_token

        except Exception as e:
            raise RuntimeError(f"获取 Access Token 异常：{str(e)}") from e

    def send_template_msg(
            self,
            openid: str,
            template_id: str,
            data: Dict[str, Dict[str, str]],
            url: Optional[str] = None,
            miniprogram: Optional[Dict[str, str]] = None
    ) -> Dict[str, Any]:
        """
        发送公众号模板消息（需公众号已配置模板，用户已关注）
        :param openid: 接收消息的用户openid（每个用户对公众号唯一）
        :param template_id: 模板消息ID（从公众号后台「模板消息」模块获取）
        :param data: 模板消息内容（格式：{"字段名": {"value": "内容", "color": "#173177"}}）
        :param url: 可选，点击模板消息跳转的H5链接（非必填）
        :param miniprogram: 可选，点击跳转的小程序配置（格式：{"appid": "小程序ID", "pagepath": "页面路径"}）
        :return: 微信接口返回结果（含 msgid 表示发送成功）
        """
        # 1. 参数基础校验
        if not (openid and template_id and data):
            raise ValueError("openid、template_id、data 为必填参数，不可为空")

        # 2. 组装请求数据
        request_data = {
            "touser": openid,
            "template_id": template_id,
            "data": data
        }
        # 补充可选参数
        if url:
            request_data["url"] = url
        if miniprogram and isinstance(miniprogram, dict):
            request_data["miniprogram"] = miniprogram

        # 3. 发送请求
        try:
            access_token = self._get_access_token()
            headers = {"Content-Type": "application/json; charset=utf-8"}
            params = {"access_token": access_token}

            response = requests.post(
                url=self._TEMPLATE_MSG_URL,
                params=params,
                json=request_data,
                headers=headers,
                timeout=10
            )
            response.raise_for_status()
            result = response.json()

            # 处理模板消息发送错误
            if "errcode" in result and result["errcode"] != 0:
                raise ValueError(f"模板消息发送失败：{result['errmsg']}（错误码：{result['errcode']}）")

            return result

        except Exception as e:
            raise RuntimeError(f"发送模板消息异常：{str(e)}") from e

    def send_text_msg(self, openid: str, content: str) -> Dict[str, Any]:
        """
        发送公众号文本消息（需公众号已认证，用户已关注，且在48小时互动期内）
        :param openid: 接收消息的用户openid
        :param content: 文本内容（不超过2048字符，支持换行\n）
        :return: 微信接口返回结果
        """
        # 1. 参数校验
        if not (openid and content):
            raise ValueError("openid 和 content 为必填参数，不可为空")
        if len(content) > 2048:
            raise ValueError("文本消息内容长度不能超过2048字符")

        # 2. 组装请求数据
        request_data = {
            "touser": openid,
            "msgtype": "text",
            "text": {"content": content}
        }

        # 3. 发送请求
        try:
            access_token = self._get_access_token()
            headers = {"Content-Type": "application/json; charset=utf-8"}
            params = {"access_token": access_token}

            response = requests.post(
                url=self._TEXT_MSG_URL,
                params=params,
                json=request_data,
                headers=headers,
                timeout=10
            )
            response.raise_for_status()
            result = response.json()

            # 处理文本消息发送错误
            if "errcode" in result and result["errcode"] != 0:
                raise ValueError(f"文本消息发送失败：{result['errmsg']}（错误码：{result['errcode']}）")

            return result

        except Exception as e:
            raise RuntimeError(f"发送文本消息异常：{str(e)}") from e


# ------------------------------
# 使用示例
# ------------------------------
if __name__ == "__main__":
    # 1. 初始化公众号配置（替换为你的 appid 和 appsecret）
    app_id = "wxa36512f44bdbc221"
    app_secret = "b04c124212766f8c419a3241ca94f143"
    wechat_msg = WechatOfficialAccountMsg(app_id=app_id, app_secret=app_secret)
    user_openid = "o52C55jQ_xeZy04VxREWM3zg6S9Y"
    # user_openid = ""
    # 2. 示例1：发送模板消息
    try:
        template_id = "xTNd0DWvdLQV4hQXnjJYMxLMDXKU2xyTadfFTRx3tCw"
        # 模板内容（字段名需与模板一致，color可选）
        json_data = {
            "amount7": {"value": "20.00元"},
            "thing2": {"value": "zhangsan"},
            "thing3": {"value": "擀面皮"},
            "character_string5": {"value": "20"},
            "phrase8": {"value": "待审核"}
        }
        print(f'json_data={json_data}')
        # 发送模板消息
        template_result = wechat_msg.send_template_msg(
            openid=user_openid,
            template_id=template_id,
            data=json_data,
            # url="https://www.jd.com/"
        )
        print(f'template_result={template_result}')
        print(f"模板消息发送成功，msgid：{template_result['msgid']}")
    except Exception as e:
        print(f"模板消息发送失败：{str(e)}")
